package common;

import java.io.File;
import java.io.FileFilter;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Vector;

import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

/**
 * Crée un modèle de données exploitables par un JTree.<br>
 * Ce modèle représente une arborescence de fichiers.
 * @author Cette classe est une version modifée du site <br>
 * http://www.javaprogrammingforums.com/java-swing-tutorials/7944-how-use-jtree-create-file-system-viewer-tree.html <br>
 */
public class TreeModelExplorer implements TreeModel, Serializable {
	
	
	private static final long serialVersionUID = 1L;
	
	/**
	 * Le fichier racine du répertoire
	 */
	private File _root;
	
	/**
	 * Un vecteur contenant l'ensemble des écouteurs du modèle.
	 */
	private Vector<TreeModelListener> _listeners = new Vector<TreeModelListener>();
	
	/**
	 * Détermine si les fichiers cachés sont affichés ou non.
	 */
	private boolean _showHiddenFiles;

	/**
	 * Crée un nouveau modèle de données pour l'exploration de fichiers et spécifie le répertoire racine du modèle.
	 * @param rootDirectory :
	 * Le chemin vers le répertoire racine de ce modèle.
	 */
	public TreeModelExplorer(File rootDirectory) {
		_root = rootDirectory;
		_showHiddenFiles = false;
	}
	
	/**
	 * Crée un nouveau modèle de données pour l'exploration de fichiers et spécifie le répertoire racine du modèle.
	 * @param rootDirectory :
	 * Le chemin vers le répertoire racine de ce modèle.
	 * @param showHiddenFiles :
	 * Détermine si oui ou non les fichiers cachés doivent être pris en compte par ce modèle
	 */
	public TreeModelExplorer(File rootDirectory, boolean showHiddenFiles) {
		_root = rootDirectory;
		_showHiddenFiles = showHiddenFiles;
	}
	
	/**
	 * Retourne le fichier racine du modèle.
	 */
	public Object getRoot() {
		return _root;
	}
	
	/**
	 * Récupère le noeud enfant à l'indice spécifié.
	 * @param parent :
	 * Le fichier parent.
	 * @param index :
	 * L'indice de l'enfant recherché parmi l'ensemble des enfants du fichier parent.<br>
	 * L'indice commence à 0.
	 */
	public Object getChild(Object parent, int index) {
		// Cast du noeud parent en File
		File directory = (File) parent;
		
		// Récupération de la liste des fichiers enfants (fichiers + répertoires)
		String[] children = null;
		
		children = getChildren(directory);
		
		// Création et retour d'un noeud enfant
		return new TreeFile(directory, children[index]);
	}
	
	/**
	 * Obtient l'ensemble des fichiers et répertoires enfants du dossier racine directory
	 * @param directory
	 * Le dossier racine à partir duquel vont être retournés les enfants.
	 * @return
	 * un tableau contenant le chemin absolu des enfants du répertoire racine
	 */
	private String[] getChildren(File directory)
	{
		String[] children = null;
		if (_showHiddenFiles)
		{
			children = directory.list();
		}
		else
		{
			children = listNoHiddenFiles(directory);
		}
		return children;
	}
	
	/**
	 * Liste l'ensemble des fichiers et répertoires enfants NON cachés, à partir du dossier racine directory.
	 * @param directory
	 * Le dossier racine à partir duquel seront listés tous les fichiers et dossiers non-cachés.
	 * @return
	 * L'ensemble des fichiers et dossiers enfants du dossier racine et qui sont non cachés.
	 */
	private String[] listNoHiddenFiles(File directory)
	{
		String[] children = null;
		File[] files = directory.listFiles(new HiddenFilter());
		if (files != null)
		{
			children = new String[files.length];
			for (int i=0; i<files.length; i++)
			{
				children[i] = files[i].getName();
			}
		}
		return children;
	}
	
	/**
	 * Un filtre pour ne laisser passer que les fichiers non-cachés 
	 * lorsque l'on liste un répertoire
	 */
	class HiddenFilter implements FileFilter {
		
	   public HiddenFilter()
	   {}
	   
	   /**
	    * N'accepte que les fichiers qui ne sont pas cachés
	    */
		@Override
		public boolean accept(File f) {
			if (f.isHidden()) return false;
			return true;
		}
	}

	/**
	 * Retourne le nombre d'enfants dans le noeud parent. <br>
	 * Retourne 0 si le noeud n'a pas d'enfants, ou si le noeud est une feuille.
	 * @param parent :
	 * Le noeud parent à partir duquel chercher les enfants.
	 */
	public int getChildCount(Object parent) {
		// Cast du noeud parent en File
		File file = (File) parent;
		
		// Le noeud est un répertoire
		if (file.isDirectory())
		{
			// Récupération des fichiers/répertoires enfants
			String[] fileList = getChildren(file);
			
			// Il y a des enfants
			if (fileList != null)
			{
				// Retourner le nombre d'enfants
				return fileList.length;
			}
		}
		// Le noeud est un fichier: c'est une feuille.
		return 0;
	}

	/**
	 * Indique si le noeud est une feuille.
	 * @param node :
	 * Le fichier à tester.
	 * @return True si le noeud est un fichier (File), false sinon.
	 */
	public boolean isLeaf(Object node) {
		// Cast du noeud en File
		File file = (File) node;
		
		// Retour du test "est-ce un fichier ?"
		return file.isFile();
	}

	/**
	 * Retourne l'indice de l'enfant recherché parmi l'ensemble des enfants du parent.
	 * Si l'enfant recherché n'existe pas, la méthode retourne -1
	 * @param parent :
	 * Le fichier parent dans lequel chercher l'enfant.
	 * @param child :
	 * L'enfant dont l'indice doit être cherché.
	 */
	public int getIndexOfChild(Object parent, Object child)
	{
		// Cast du noeud parent en File
		File directory = (File) parent;
		
		// Cast du noeud enfant en File
		File file = (File) child;
		
		// Récupération de la liste des enfants du parent
		String[] children = getChildren(directory);
		
		// Pour tous les enfants du parent
		for (int i = 0; i < children.length; i++)
		{
			
			// L'enfant courant est-il l'enfant recherché ?
			if (file.getName().equals(children[i]))
			{
				// L'enfant courant et l'enfant recherché sont identiques
				// On retourne l'indice du fils identique
				return i;
			}
		}
		
		// L'enfant recherché n'est pas un enfant du parent.
		return -1;

	}
	
	public void valueForPathChanged(TreePath path, Object value)
	{
		// Récupération de l'ancien fichier
		File oldFile = (File) path.getLastPathComponent();
		
		// Récupération du chemin du dossier parent de l'ancien fichier
		String fileParentPath = oldFile.getParent();
		
		// Récupération du nom du nouveau fichier
		String newFileName = (String) value;
		
		// Création d'un nouveau fichier ayant pour parent le dossier parent de l'ancien fichier.
		File targetFile = new File(fileParentPath, newFileName);
		
		// Renomme l'ancien fichier avec le nom du nouveau fichier
		oldFile.renameTo(targetFile);
		
		// Récupération du répertoire parent du fichier renommé
		File parent = new File(fileParentPath);
		
		// Récupération de l'indice du fichier renommé
		int[] changedChildrenIndices = { getIndexOfChild(parent, targetFile) };
		
		// Récupération du fichier renommé
		Object[] changedChildren = { targetFile };
		
		// Notification du changement procédé dans l'arborescence en précisant le parent, l'indice et le fichier renommé
		notifyTreeNodesChanged(path.getParentPath(), changedChildrenIndices, changedChildren);
	}

	/**
	 * Notifie les écouteurs de l'évènement TreeNodesChanged.
	 * @param parentPath
	 * Le chemin vers le parent des noeuds qui ont été modifiés.
	 * @param indices
	 * Les indices des enfants qui ont été modifiés.
	 * @param children
	 * Les noeuds qui ont été modifiés.
	 */
	private void notifyTreeNodesChanged(TreePath parentPath, int[] indices, Object[] children) {
		// Création du paramètres de l'évènement à notifier
		TreeModelEvent event = new TreeModelEvent(this, parentPath, indices, children);
		
		// Création d'un itérateur pour parcourir le vecteur d'écouteurs
		Iterator<TreeModelListener> iterator = _listeners.iterator();
		TreeModelListener listener = null;
		
		// Pour tous les écouteurs de ce modèle
		while (iterator.hasNext()) {
			
			// Récupération de l'écouteur courant
			listener = (TreeModelListener) iterator.next();
			
			// Notification de l'évènement TreeNodesChanged sur l'écouteur courant
			listener.treeNodesChanged(event);
		}
	}

	public void addTreeModelListener(TreeModelListener listener) {
		_listeners.add(listener);
	}

	public void removeTreeModelListener(TreeModelListener listener) {
		_listeners.remove(listener);
	}

	/**
	 * Un noeud de modèle arborescent représentant un fichier et son parent.
	 */
	private class TreeFile extends File {
		private static final long serialVersionUID = 1L;

		/**
		 * Crée un nouveau noeud de fichier pour modèle arborescent.
		 * @param parent :
		 * Le fichier parent de ce noeud.
		 * @param child :
		 * Le chemin du fichier enfant qui sera représenté par ce TreeFile.
		 */
		public TreeFile(File parent, String child) {
			super(parent, child);
		}
		
		/**
		 * Retourne le nom du fichier encapsulé.
		 */
		public String toString() {
			return getName();
		}
	}
	
	public boolean areHiddenFilesShown()
	{
		return _showHiddenFiles;
	}
}